//IPT.cc
#include "IPT.h"
extern "C" { int bzero(char *, int); };


IPT::IPT(){

  //Initialize the IPT table with invalid entries
  for(int i = 0; i < NumPhysPages; i++){
    iptElement[i].valid = false;
  }
  
  //Create an IPT lock to prevent race condition
  iptLock = new Lock("ipt Lock");
  evictSlot = 0;  //FIFO iterator
}

IPT::~IPT(){
  delete iptLock;
}


//find empty slot to put the entry
//ONLY used for when first loading in code
// it sets the dirty bit to false
void IPT::setElement(int vpn, int ppn,AddrSpace *processID){
  for(int i = 0; i < NumPhysPages; i++){
 
    iptLock->Acquire();

    if(!iptElement[i].valid)
      {
	//printf("%s : adding element with ID %d and vpn %d and ppn %d\n",currentThread->getName(),processID,vpn,ppn);
	
	iptElement[i].valid = true;
	iptElement[i].virtualPage = vpn;
	iptElement[i].physicalPage = ppn;
	iptElement[i].readOnly = false;
	iptElement[i].dirty = false;
	iptElement[i].use = false;
	iptElement[i].processID = processID;
	
	processID->pageTableInfo[vpn].location = InMainMemory;


	iptLock->Release();
	//printf("element added with vpn %d and ppn %d\n",vpn,ppn);
	return;

      }
  
    iptLock->Release();
  }
  
  //else
  printf("IPT::setElement() all elements full\n");
}


/*
  function used to find physical page number if it is in the 
  IPT. If not then a -1 will be returned, which means an IPT miss
*/
int IPT::findPPN(int vpn, AddrSpace *processID){

  //printf("looking for vpn %d and ID %d\n",vpn, processID);

  //Search whether the process's requested vpn is in the IPT
  for(int i = 0; i < NumPhysPages; i++){
   
    //iptLock->Acquire();
		 
    if(iptElement[i].processID == processID && iptElement[i].valid)
      {

	if(iptElement[i].virtualPage == vpn) {
	  //printf("%s : ppn %d found for vpn %d and ID %d\n",currentThread->getName(),iptElement[i].physicalPage,vpn,processID);
					 
	  // iptLock->Release();
					 
	  return iptElement[i].physicalPage;
	}
      }
		 
    //iptLock->Release();
  }
    
  //if not found, not in main memory, IPT miss
  //printf("%s: IPT::findPPN() could not find entry with vpn %d and processID %d\n",currentThread->getName(),vpn,processID);
  return -1;
}


//evict IPT page slot using FIFO 
void IPT::evictFirstSlot(){
 
  iptLock->Acquire();

  bool found = false;

  //use for loop to keep the FIFO eviction order
  for(int i = 0; i < NumPhysPages && !found; i++){ 
    
    //Find a valid IPT element
    if(iptElement[evictSlot].valid){
      printf("Process %d : evictFirstSlot() evicting IPT slot %d that contains page with vpn %d , ppn %d , processID %d",
				 currentThread->space,evictSlot,iptElement[evictSlot].virtualPage,iptElement[evictSlot].physicalPage,iptElement[evictSlot].processID);  

		if(iptElement[evictSlot].dirty){
			printf(", dirty true(Writes to swap)\n");
		}
		else printf("\n");

      found = true;

      //update the TLB only if the evicted entry has the same processID
      if(iptElement[evictSlot].processID == currentThread->space){

	//Find tlb entry that is the same as the evicted page
	for(int j = 0; j < 4; j++){
	  if(machine->tlb[j].virtualPage == iptElement[evictSlot].virtualPage &&
	     machine->tlb[j].physicalPage == iptElement[evictSlot].physicalPage){
					  
	    //just in case, if the tlb entry for evicted page was recently set to dirty 
	    //we should update this information
	    if( machine->tlb[j].dirty){					 
	      iptElement[evictSlot].dirty = true;
	    }

	    //Invalidate the entry
	    IntStatus oldLevel = interrupt->SetLevel(IntOff);

	    machine->tlb[j].valid = false;
	    machine->tlb[j].physicalPage = -1;
	    machine->tlb[j].virtualPage = -1;
	    //printf("tlb evicted page found\n");

	    (void)interrupt->SetLevel(oldLevel); 
	  }
	}
      }
      
      //only write evicted page to the swap file if it is dirty
      if(iptElement[evictSlot].dirty){
	
	//Position to write in swap file
	int swapFilePosition;
	
	//Each page of a process page table has its own swap file position, thus if its swap file position has not yet been set, then set it.
	if(iptElement[evictSlot].processID->pageTableInfo[iptElement[evictSlot].virtualPage].swapFilePosition == -1){

	  //Swap file position not set yet, so search through swap file bitmap for an available page in the swap file
	  swapFilePosition = swapFileBitmap->Find();

	  if(swapFilePosition != -1){
	    //Available page in swap file found, set the IPT element
	    iptElement[evictSlot].processID->pageTableInfo[iptElement[evictSlot].virtualPage].swapFilePosition = swapFilePosition;
	  }
	  
	  else {
	    //Swap file memory full
	    printf("\n\nevictFirstSlot() : swap File out of room! data lost, halting nachos!\n\n");
	    interrupt->Halt();
	  }
				
	}
			  
	//Since swap file position is now set, write the evicted page to the swap file
	swapFilePosition = iptElement[evictSlot].processID->pageTableInfo[iptElement[evictSlot].virtualPage].swapFilePosition;
	swapFile->WriteAt(&(machine->mainMemory[iptElement[evictSlot].physicalPage*PageSize]),PageSize,PageSize*swapFilePosition);

	printf("Page dirty - Writing to swap file at position %d for vpn %d of processID %d\n",swapFilePosition, iptElement[evictSlot].virtualPage, iptElement[evictSlot].processID);
			  
	//Change the location of the page in the page table to swap file
	iptElement[evictSlot].processID->pageTableInfo[iptElement[evictSlot].virtualPage].location = InSwapFile;	
      }

      //evicted page is not dirty, thus we can still use the page in the executable
      else {
			
	int evictSlotVPN = iptElement[evictSlot].virtualPage;
	AddrSpace *evictSlotProcess = iptElement[evictSlot].processID;
	
	//Make sure it is a valid program with code pages
	if(iptElement[evictSlot].processID->numCodePages!=0){
	  evictSlotProcess->pageTableInfo[evictSlotVPN].location = InExecutable;
	}
	else {
	  printf("NO CODE PAGES ERROR\n");
	}
	 
      }

      //Clear the evicted page from main memory, so a free page in main memory is now available
      MMLock->Acquire();
      mainMemory->Clear(iptElement[evictSlot].physicalPage);
      int physPage = iptElement[evictSlot].physicalPage;
      bzero(&(machine->mainMemory[PageSize*physPage]), PageSize); //clear the page we want to replace
      MMLock->Release();

      //Clear the evicted page from IPT
      //set valid to false and when we do a setElement()
      //this will be the first to be relpaced
      iptElement[evictSlot].valid = false;
      iptElement[evictSlot].physicalPage = -1;
    }


    //increment the value of evictSlot
    if(evictSlot == NumPhysPages){
      evictSlot = 0;
    }
    else {
      evictSlot++;
    }

  }

  iptLock->Release();
}



//Evict a page from the IPT randomly 
//Check for comments in FIFO page eviction for more details
void IPT::evictRandomSlot(){
  
  iptLock->Acquire();

  //Random evicted page value
  int randSlot;
  bool found = false;
     
  while(!found){
    
    randSlot = rand()%NumPhysPages;
    
    if(iptElement[randSlot].valid){
      
      found = true;

      printf("Process %d : evictRandomSlot() evicting IPT slot %d that contains page with vpn %d , ppn %d , processID %d",
	     currentThread->space,randSlot,iptElement[randSlot].virtualPage,iptElement[randSlot].physicalPage,iptElement[randSlot].processID);  
		if(iptElement[randSlot].dirty){
			printf(", dirty true(Writes to swap)\n");
		}
		else printf("\n");
     
      //update the TLB only if the evicted entry has the same processID
      if(iptElement[randSlot].processID == currentThread->space){
	for(int j = 0; j < 4; j++){
	  if(machine->tlb[j].virtualPage == iptElement[randSlot].virtualPage &&
	     machine->tlb[j].physicalPage == iptElement[randSlot].physicalPage){

	    //just in case, if the tlb entry for evicted page was recently set to dirty 
	    //we should update this information
	    if( machine->tlb[j].dirty){					 
	      iptElement[randSlot].dirty = true;
	    }

	    //Turn off interrupts before modifying TLB
	    IntStatus oldLevel = interrupt->SetLevel(IntOff);

	    machine->tlb[j].valid = false;
	    machine->tlb[j].physicalPage = -1;
	    machine->tlb[j].virtualPage = -1;

	    (void)interrupt->SetLevel(IntOff);
	  }
	}
      }
      
      //only write evicted page to the swap file if it is dirty
      if(iptElement[randSlot].dirty){
           
	int swapFilePosition;
	
	if(iptElement[randSlot].processID->pageTableInfo[iptElement[randSlot].virtualPage].swapFilePosition == -1){		    
	  swapFilePosition = swapFileBitmap->Find();
	  if(swapFilePosition != -1){
	    iptElement[randSlot].processID->pageTableInfo[iptElement[randSlot].virtualPage].swapFilePosition = swapFilePosition;
	  }
	  
	  else {
	 
	    printf("\n\nevictFirstSlot() : swap File out of room! data lost, halting nachos!\n\n");
	    interrupt->Halt();
	  }
				
	}
			  
	swapFilePosition = iptElement[randSlot].processID->pageTableInfo[iptElement[randSlot].virtualPage].swapFilePosition;
	
	swapFile->WriteAt(&(machine->mainMemory[iptElement[randSlot].physicalPage*PageSize]),PageSize,PageSize*swapFilePosition);			  

	printf("Page dirty - Writing to swap file at position %d for vpn %d of processID %d\n",swapFilePosition, iptElement[randSlot].virtualPage, iptElement[randSlot].processID);
			  
			  
	iptElement[randSlot].processID->pageTableInfo[iptElement[randSlot].virtualPage].location = InSwapFile;	
     
      }

      //evicted page is not dirty
      else {

	int randSlotVPN = iptElement[randSlot].virtualPage;
	AddrSpace *randSlotProcess = iptElement[randSlot].processID;
	
	
	if(iptElement[randSlot].processID->numCodePages!=0){
	   
	  randSlotProcess->pageTableInfo[randSlotVPN].location = InExecutable;
	  
	}
	else {
	  printf("NO CODE PAGES ERROR\n");
	}

      }


      MMLock->Acquire();
      mainMemory->Clear(iptElement[randSlot].physicalPage);
      int physPage = iptElement[randSlot].physicalPage;
      bzero(&(machine->mainMemory[PageSize*physPage]), PageSize); //clear the page we want to replace
      MMLock->Release();

      //set valid to false and when we do a setElement()
      //this will be the first to be relpaced
      iptElement[randSlot].valid = false;
      iptElement[randSlot].physicalPage = -1;
    }
  }

  iptLock->Release();
}



//Set the dirty bit for a page in the IPT with the corresponding vpn
void IPT::setDirtyBit(int vpn){

  //Search the whole IPT for the page
  for(int i = 0; i < NumPhysPages; i++){

    //Found the correct and valid entry in the IPT
    if(iptElement[i].processID == currentThread->space  && iptElement[i].virtualPage == vpn && iptElement[i].valid){
		  
      //Make sure it is not a page in the executable!
      if(iptElement[i].virtualPage > currentThread->space->numCodePages - 1){
	iptElement[i].dirty = true;
	currentThread->space->pageTable[vpn].dirty = true;
	//iptLock->Release();
	//printf("%d - setDirtyBit(): dirty bit for vpn %d set!\n", currentThread->space, vpn);
	return;
      }

      else {
	printf("ERROR: VPN %d to set dirty is less than the number of code pages %d!!\n", vpn,  currentThread->space->numCodePages - 1);
	interrupt->Halt();
      }
    }
  }
  
  

  //if it gets here, then the requested vpn page is not in the IPT
  printf("%d - setDirtyBit() : iptElement with vpn %d not found!\n",currentThread->space,vpn);
  printiptElements();
  interrupt->Halt();
}


//Set the bit for an element in the IPT as being used
//NOT USED FOR PROJECT 3
void IPT::setUseBit(int vpn){
  /*  for(int i = 0; i < NumPhysPages; i++){
    
  if(iptElement[i].processID == currentThread->space &&
  iptElement[i].virtualPage == vpn){
  //      if(iptElement[i].virtualPage > currentThread->space->numCodePages - 1){
  if(1){
  iptElement[i].use = true;
  currentThread->space->pageTable[vpn].use = true;
  return;
  }
  else {
  printf(" setUseBit() : code page cannot be dirty!\n");
  interrupt->Halt();
  }
  }
  }

  //else
  printf(" setUseBit() : iptElement with vpn %d not found with processID %d!\n",vpn,currentThread->space);
  printiptElements();
  interrupt->Halt();*/
}



//function used to remove ipt elemements but not necessarily replace
//them with anything, which is the case for when a thread or process 
//exits.
void IPT::clearElement(int vpn){
  

  for(int i = 0; i  < NumPhysPages; i++){ 

    iptLock->Acquire();

    if(iptElement[i].processID == currentThread->space)
      {
	if(iptElement[i].virtualPage == vpn &&
	   iptElement[i].valid){
	  iptElement[i].valid = false;
	    
	  iptLock->Release();
	  return;
	}
      }

    iptLock->Release();
  }


  //else 
  printf(" clearElement() : iptElement not found!\n");
}



//function used to remove all ipt elements that has the process ID pass in
//this is so to avoid having ipt elements whose processes are already destroyed
void IPT::clearProcessElements(AddrSpace *processID){
  if(processID == NULL){
    printf("NULL Process ID elements asked to be cleared from IPT\n");
  }

  for(int i = 0; i  < NumPhysPages; i++){ 
    iptLock->Acquire();
	  
    if(iptElement[i].processID == processID && iptElement[i].valid) {
      iptElement[i].valid = false;
    }
	  
    iptLock->Release();
  }
	

  printf("\nCleared elements with processID %d from IPT\n", processID);

}

void IPT::printiptElements(){
  //lets only print the ones that are not empty
  /*  for(int i = 0; i < NumPhysPages; i++){
      if(iptElement[i].valid){
      printf("index %d vpn %d ppn %d valid bit %d processID %d dirtybit %d\n",i,iptElement[i].virtualPage, iptElement[i].physicalPage,iptElement[i].valid,iptElement[i].processID,iptElement[i].dirty);
      }
      }*/
}
